home *** CD-ROM | disk | FTP | other *** search
/ Software Vault: The Gold Collection / Software Vault - The Gold Collection (American Databankers) (1993).ISO / cdr49 / mtl100je.zip / THREADS.CPP < prev    next >
C/C++ Source or Header  |  1993-05-06  |  29KB  |  847 lines

  1. //--------------------------------------------------------------------------
  2. //
  3. //      THREADS.CPP: body of DOS multithreading library.
  4. //      Copyright (c) J.English 1993.
  5. //      Author's address: je@unix.brighton.ac.uk
  6. //
  7. //      Permission is granted to use copy and distribute the
  8. //      information contained in this file provided that this
  9. //      copyright notice is retained intact and that any software
  10. //      or other document incorporating this file or parts thereof
  11. //      makes the source code for the library of which this file
  12. //      is a part freely available.
  13. //
  14. //--------------------------------------------------------------------------
  15. //
  16. //      Note: this library is highly DOS specific and hence non-portable.
  17. //      It also involves the use of assembly language and interrupt
  18. //      functions, so it is also very compiler-specific and will need
  19. //      modification for use with compilers other than Borland C++ 3.0
  20. //      or later.
  21. //
  22. //      Revision history:
  23. //      1.0     March 1993      Initial coding
  24. //
  25. //--------------------------------------------------------------------------
  26.  
  27. #include "threads.h"
  28. #include <dos.h>
  29.  
  30. //--------------------------------------------------------------------------
  31. //
  32. //      Assembler sequences.
  33. //
  34. #define DISABLE     asm { pushf; cli; }     // save and disable interrupts
  35. #define ENABLE      asm { popf; }           // restore interrupt state
  36. #define PUSHAD      asm { db 0x66, 0x60; }  // push extended registers
  37. #define POPAD       asm { db 0x66, 0x61; }  // pop extended registers
  38. #define PUSH_FS     asm { db 0x0F, 0xA0; }  // push FS register
  39. #define PUSH_GS     asm { db 0x0F, 0xA8; }  // push GS register
  40. #define POP_FS      asm { db 0x0F, 0xA1; }  // pop FS register
  41. #define POP_GS      asm { db 0x0F, 0xA9; }  // pop GS register
  42.  
  43.  
  44. //--------------------------------------------------------------------------
  45. //
  46. //      Typedefs and constants.
  47. //
  48. typedef void interrupt (*Handler)(...);   // interrupt handler type
  49. typedef volatile unsigned Register;       // interrupt handler parameter type
  50.  
  51. const unsigned MIN_STACK  = 512;          // minimum stack size
  52. const long     DAY_LENGTH = 1573040L;     // length of day in timer ticks
  53.  
  54.  
  55. //--------------------------------------------------------------------------
  56. //
  57. //      Global (static) variables.
  58. //
  59. static DOSThreadManager* current = 0;     // current thread
  60. static DOSThreadManager* ready   = 0;     // queue of ready threads
  61. static DOSThreadManager* delayed = 0;     // queue of delayed threads
  62. static DOSThread* mainthread     = 0;     // thread for execution of "main"
  63.  
  64. static unsigned mainsetup = 0;            // flag set when constructing "main"
  65. static unsigned slicesize = 1;            // length of timeslice in clock ticks
  66. static unsigned i386;                     // flag set if executing on 386/486
  67.  
  68. static volatile unsigned threadcount = 0; // number of active threads
  69. static volatile unsigned breakflag = 0;   // flag set by control-break
  70. static volatile long     nextslice = 0;   // tick count for next timeslice
  71.  
  72. static volatile long far* currtime = (volatile long far*) MK_FP(0x40,0x6C);
  73. static volatile char far* midnight = (volatile char far*) MK_FP(0x40,0x70);
  74.                                           // timer values in BIOS data area
  75.  
  76. //--------------------------------------------------------------------------
  77. //
  78. //      Interrupt function pointers.
  79. //
  80. static Handler old_timer;                 // original timer interrupt handler
  81. static Handler old_dos;                   // original DOS services handler
  82. static Handler old_break;                 // original control-break handler
  83. static Handler old_error;                 // original critical error handler
  84.  
  85. //--------------------------------------------------------------------------
  86. //
  87. //      Identify CPU.
  88. //
  89. //      This function is needed so that on 386/486 processors the
  90. //      extended registers can be saved as part of a thread's context.
  91. //      This is necessary in case the thread is interrupted during a
  92. //      routine that relies on these registers being preserved.  On
  93. //      the 386 and above, bits 12 - 14 of the flag register can be
  94. //      written to; on earlier processors, they are either always 1
  95. //      (8086) or always 0 (286).  The global variable "i386" is set
  96. //      to 1 if we are executing on a 386 or above, and 0 otherwise.
  97. //
  98. void interrupt cputype ()
  99. {
  100.     //--- Assume a 386 or above to start with
  101.     i386 = 1;
  102.  
  103.     //--- Test for an 8086 (bits 12 - 15 of flags register always 1)
  104.     _FLAGS = 0;
  105.     if ((_FLAGS & 0xF000) == 0xF000)
  106.         i386 = 0;
  107.  
  108.     //--- Test for a 286 (bits 12 - 14 of flags register always 0)
  109.     _FLAGS = 0x7000;
  110.     if ((_FLAGS & 0x7000) == 0)
  111.         i386 = 0;
  112. }
  113.  
  114. //--------------------------------------------------------------------------
  115. //
  116. //      Class DOSNullThread.
  117. //
  118. //      A concrete derivation of DOSThread used for the null thread and
  119. //      the main thread.  The main body just sits in an infinite loop.
  120. //      A minimal stack is allocated for the purpose.
  121. //
  122. class DOSNullThread : public DOSThread
  123. {
  124.   public:
  125.     DOSNullThread ()    : DOSThread (MIN_STACK)     { }
  126.     
  127.   protected:
  128.     virtual void main ();
  129. };
  130.  
  131. void DOSNullThread::main ()
  132. {
  133.     for (;;) ;                            // do nothing
  134. }
  135.  
  136.  
  137. //--------------------------------------------------------------------------
  138. //
  139. //      Class DOSCallMonitor.
  140. //
  141. //      This class is a monitor which protects against re-entrant DOS
  142. //      calls.  It contains the DOS interrupt handler which uses "lock"
  143. //      and "unlock" to prevent DOS being re-entered.  Since interrupt
  144. //      routines must be static and thus have no "this" pointer, there
  145. //      is a single instance of this class declared which the interrupt
  146. //      handler can use when calling "lock" and "unlock".
  147. //
  148. class DOSCallMonitor : public DOSMonitor
  149. {
  150.   public:
  151.     static void interrupt dos_int         // handle DOS service calls
  152.         (Register, Register, Register, Register,
  153.          Register, Register, Register, Register,
  154.          Register, Register, Register, Register);
  155. };
  156.  
  157. static DOSCallMonitor dos;                // instance used by DOS handler
  158.  
  159. //--------------------------------------------------------------------------
  160. //
  161. //      Class DOSThreadManager.
  162. //
  163. //      This is a support class used for maintaining queues of threads.
  164. //      A queue is represented as a circular list of DOSThreadManagers.
  165. //      Each queue is headed by a DOSThreadManager with a null "thread"
  166. //      pointer, so that queues and threads can be treated in a unified
  167. //      manner -- it guarantees that queues will never be empty, and
  168. //      simplifies moving threads around.  The only exception to this
  169. //      is the header for the ready queue, which has a pointer to the
  170. //      null thread instead of a null pointer.  This guarantees that the
  171. //      ready queue always contains a runnable thread and that the null
  172. //      thread will only ever be executed when the ready queue contains
  173. //      no other runnable threads.  The static member functions are also
  174. //      included in this class so they can access the private parts of
  175. //      the threads being controlled.
  176. //
  177. class DOSThreadManager
  178. {
  179.   public:
  180.     DOSThreadManager (DOSThread* t = 0)   : thread (t), critflag (0)
  181.                                           { next = prev = this; }
  182.     void move (DOSThreadManager* t,
  183.                DOSThread::State s);       // move to position before "t"
  184.  
  185.     DOSThreadManager* next;               // next entry in list
  186.     DOSThreadManager* prev;               // previous entry in list
  187.     DOSThread*        thread;             // thread for this entry
  188.     unsigned far*     stkptr;             // stack pointer
  189.     unsigned long     wakeup;             // wakeup time in clock ticks
  190.     unsigned          critflag;           // flag set during critical errors
  191.  
  192.     static void far start (DOSThread* t)  // execute thread and then shut down
  193.                                           { t->main(); t->terminate(); }
  194.  
  195.     static void           cleanup ();     // restore interrupt vectors at exit
  196.     static void           create ();      // create main & null threads
  197.     static void interrupt destroy ();     // destroy main & null threads
  198.     static void interrupt schedule ();    // schedule next thread
  199.     static void interrupt timer_int ();   // handle timer interrupts
  200.     static void interrupt break_int ();   // handle control-break
  201.     static void interrupt error_int       // handle critical errors
  202.         (Register, Register, Register,
  203.          Register, Register, Register,
  204.          Register, Register, Register);
  205. };
  206.  
  207. //--------------------------------------------------------------------------
  208. //
  209. //      Timer interrupt handler.
  210. //
  211. void interrupt DOSThreadManager::timer_int ()
  212. {
  213.     //--- call old timer handler
  214.     old_timer ();
  215.  
  216.     //--- move current thread to back of ready queue at end of timeslice
  217.     long now = *currtime;
  218.     if (nextslice >= DAY_LENGTH && *midnight != 0)
  219.         nextslice -= DAY_LENGTH;
  220.     if (slicesize > 0 && now >= nextslice && current == ready->next)
  221.         current->move (ready, DOSThread::READY);
  222.  
  223.     //--- make threads with expired delays runnable
  224.     DOSThreadManager* t = delayed->next;
  225.     DOSThreadManager* r = ready->next;
  226.     while (t != delayed)
  227.     {   if (t->wakeup >= DAY_LENGTH && *midnight != 0)
  228.             t->wakeup -= DAY_LENGTH;
  229.         if (now < t->wakeup)
  230.             break;
  231.         t = t->next;
  232.         t->prev->move (r, DOSThread::READY);
  233.     }
  234.  
  235.     //--- reschedule
  236.     DOSThreadManager::schedule ();
  237. }
  238.  
  239.  
  240. //--------------------------------------------------------------------------
  241. //
  242. //      Control-break interrupt handler.
  243. //
  244. //      This routine just sets a flag for polling by individual threads.
  245. //
  246. void interrupt DOSThreadManager::break_int ()
  247. {
  248.     breakflag = 1;
  249. }
  250.  
  251.  
  252. //--------------------------------------------------------------------------
  253. //
  254. //      Critical error interrupt handler.
  255. //
  256. //      This calls the critical error handler for the current thread
  257. //      (which will always be the current one, since only one thread
  258. //      can be executing a DOS call at any one time).  "Critflag"
  259. //      is set to inform the DOS interrupt handler that a critical
  260. //      error is being handled.
  261. //
  262. void interrupt DOSThreadManager::error_int
  263.                 (Register, Register di, Register,
  264.                  Register, Register,    Register,
  265.                  Register, Register,    Register ax)
  266. {
  267.     current->critflag = 1;
  268.     ax = current->thread->DOSerror ((ax & 0xFF00) | (di & 0x00FF)) & 0xFF;
  269.     current->critflag = 0;
  270. }
  271.  
  272. //--------------------------------------------------------------------------
  273. //
  274. //      DOS service interrupt handler.
  275. //
  276. //      Since DOS is not re-entrant, this handler is required to protect
  277. //      against threads making DOS calls while there is already one in
  278. //      progress.  It uses the monitor "dos" to prevent re-entrancy.
  279. //      However, if a critical error occurs, the thread's critical error
  280. //      handler may make a DOS call (but only functions 00 to 0C).  In
  281. //      this case, the lock operation is bypassed (the thread must already
  282. //      be in a DOS function) but the function code is checked.
  283. //
  284. void interrupt DOSCallMonitor::dos_int
  285.                 (Register,    Register di, Register si, Register,
  286.                  Register es, Register dx, Register cx, Register bx,
  287.                  Register ax, Register,    Register,    Register flags)
  288. {
  289.     if (current->critflag == 0)
  290.         dos.lock ();          // prevent reentrance to DOS
  291.     else if (ax > 0x0C)
  292.         return;               // critical error, functions > 0x0C not allowed
  293.  
  294.     //--- load registers from stacked values
  295.     _FLAGS = flags;
  296.     _DI = di;
  297.     _SI = si;
  298.     _ES = es;
  299.     _DX = dx;
  300.     _CX = cx;
  301.     _BX = bx;
  302.     _AX = ax;
  303.  
  304.     //--- call old DOS handler
  305.     old_dos ();
  306.     
  307.     //--- store registers in stacked copies
  308.     ax = _AX;
  309.     bx = _BX;
  310.     cx = _CX;
  311.     dx = _DX;
  312.     es = _ES;
  313.     si = _SI;
  314.     di = _DI;
  315.     flags = _FLAGS;
  316.     
  317.     if (current->critflag == 0)
  318.         dos.unlock ();        //--- allow other threads in
  319. }
  320.  
  321. //--------------------------------------------------------------------------
  322. //
  323. //      DOSThreadManager::cleanup.
  324. //
  325. //      This is called by the thread manager destructor when the last
  326. //      thread is destroyed, or by "atexit" if a quick exit happens.  It
  327. //      just restores the original interrupt vectors, so if it is called
  328. //      multiple times during exit it won't matter.
  329. //
  330. void DOSThreadManager::cleanup ()
  331. {   
  332.     //--- unhook DOS vector by hand (can't use a DOS call to do it!)
  333.     DISABLE;
  334.     Handler far* dos = (Handler far*) MK_FP(0, 0x21*4);
  335.     *dos = old_dos;
  336.     ENABLE;
  337.  
  338.     //--- unhook other vectors
  339.     setvect (0x08, old_timer);
  340.     setvect (0x23, old_break);
  341.     setvect (0x24, old_error);
  342. }
  343.  
  344.  
  345. //--------------------------------------------------------------------------
  346. //
  347. //      DOSThreadManager::move.
  348. //
  349. //      Move the caller's queue entry from its current position to the
  350. //      position before entry "t".  This must NEVER be used to move an
  351. //      entry which marks the head of a queue!  If "t" is a null pointer,
  352. //      the entry is just unlinked from its current list and left hanging.
  353. //      If the current thread is affected, a new thread is scheduled.
  354. //
  355. void DOSThreadManager::move (DOSThreadManager* t, DOSThread::State s)
  356. {
  357.     DISABLE;
  358.  
  359.     //--- change thread status
  360.     thread->state = s;
  361.  
  362.     //--- detach thread from current queue
  363.     next->prev = prev;
  364.     prev->next = next;
  365.  
  366.     //--- attach before specified position (if any)
  367.     if (t != 0)
  368.     {   next = t;
  369.         prev = t->prev;
  370.         t->prev->next = this;
  371.         t->prev = this;
  372.     }
  373.  
  374.     ENABLE;
  375. }
  376.  
  377. //--------------------------------------------------------------------------
  378. //
  379. //      DOSThreadManager::create.
  380. //
  381. //      Register the creation of a thread by incrementing the number of
  382. //      threads, and initialise the system if it is the first thread to
  383. //      be created.
  384. //
  385. void DOSThreadManager::create ()
  386. {
  387.     if (threadcount++ == 0)
  388.     {   
  389.         //--- set "i386" if the processor being used is a 386 or above
  390.         cputype ();
  391.  
  392.         //--- create the delay queue
  393.         delayed = new DOSThreadManager;
  394.  
  395.         //--- create the ready queue and the null thread
  396.         ready = (new DOSNullThread)->entry;
  397.         ready->move (ready, DOSThread::READY);
  398.  
  399.         //--- create the main thread (heavily bodged with "mainsetup")
  400.         mainsetup = 1;
  401.         mainthread = new DOSNullThread;
  402.         mainsetup = 0;
  403.         mainthread->entry->move (ready, DOSThread::READY);
  404.  
  405.         //--- save interrupt vectors
  406.         old_timer = getvect (0x08);
  407.         old_dos   = getvect (0x21);
  408.         old_break = getvect (0x23);
  409.         old_error = getvect (0x24);
  410.         atexit (cleanup);                       // take care of sudden exits
  411.  
  412.         //--- hook interrupts (DOS interrupt last!)
  413.         setvect (0x24, Handler (error_int));
  414.         setvect (0x23, Handler (break_int));
  415.         setvect (0x08, Handler (timer_int));
  416.         setvect (0x21, Handler (DOSCallMonitor::dos_int));
  417.                                                 // DOS calls unsafe now
  418.  
  419.         //--- set the thread count and the current thread
  420.         threadcount = 1;
  421.         current = mainthread->entry;             // DOS calls safe again
  422.         DOSThreadManager::schedule ();
  423.     }
  424. }
  425.  
  426. //--------------------------------------------------------------------------
  427. //
  428. //      DOSThreadManager::destroy.
  429. //
  430. //      This registers the destruction of a thread by decrementing the
  431. //      number of threads, and shuts down the threading mechanism if the
  432. //      last thread is being destroyed.
  433. //
  434. void interrupt DOSThreadManager::destroy ()
  435. {
  436.     if (--threadcount == 0)
  437.     {   
  438.         //--- unhook interrupt vectors
  439.         cleanup ();
  440.  
  441.         //--- terminate main & null threads
  442.         current = 0;
  443.         ready->thread->state = DOSThread::TERMINATED;
  444.         mainthread->state    = DOSThread::TERMINATED;
  445.  
  446.         //--- delete main & null threads and the delay queue
  447.         delete mainthread;
  448.         delete ready->thread;
  449.         delete delayed;
  450.     }
  451. }
  452.  
  453. //--------------------------------------------------------------------------
  454. //
  455. //      DOSThreadManager::schedule.
  456. //
  457. //      Save the current thread and restore another one.  Do nothing
  458. //      if there is no current thread, or if the one to be scheduled
  459. //      is already the current thread.
  460. //
  461. void interrupt DOSThreadManager::schedule ()
  462. {
  463.     //--- disable interrupts (original state will be restored on exit)
  464.     asm { cli; }
  465.  
  466.     //--- on a 386 or above, save the extended registers
  467.     if (i386)
  468.     {   PUSH_FS;
  469.         PUSH_GS;
  470.         PUSHAD;
  471.     }
  472.  
  473.     //--- switch threads if necessary
  474.     if (current != ready->next && current != 0)
  475.     {   
  476.         //--- set time for end of timeslice
  477.         nextslice = *currtime + slicesize;
  478.  
  479.         //--- save current thread's stack pointer
  480.         current->stkptr = (unsigned far*) MK_FP(_SS,_SP);
  481.  
  482.         //--- select new current thread
  483.         current = ready->next;
  484.  
  485.         //--- restore its stack (other registers will be restored on exit)
  486.         _SS = FP_SEG (current->stkptr);
  487.         _SP = FP_OFF (current->stkptr);
  488.     }
  489.  
  490.     //--- on a 386 or above, restore the extended registers
  491.     if (i386)
  492.     {   POPAD;
  493.         POP_GS;
  494.         POP_FS;
  495.     }
  496. }
  497.  
  498. //--------------------------------------------------------------------------
  499. //
  500. //      DOSThread::DOSThread.
  501. //
  502. //      Construct a new thread.  All new threads are kept terminated
  503. //      until it is certain they can be started, and are then left in
  504. //      limbo until they are explicitly started using "run".
  505. //
  506. DOSThread::DOSThread (unsigned stacksize)
  507.     : stack (mainsetup ? 0 :
  508.              new char [stacksize > MIN_STACK ? stacksize : MIN_STACK]),
  509.       entry (new DOSThreadManager (this)),
  510.       state (TERMINATED)
  511. {
  512.     //--- leave thread terminated if any allocation failures have occurred
  513.     if (!mainsetup && stack == 0)
  514.         return;                         // stack not allocated
  515.     if (entry == 0)
  516.         return;                         // thread queue entry not allocated
  517.         
  518.     //--- register thread creation
  519.     DOSThreadManager::create ();
  520.  
  521.     //--- initialise new thread (for all but main thread)
  522.     if (!mainsetup)
  523.     {
  524.         //--- set up stack pointer
  525.         entry->stkptr = (unsigned*)(stack + stacksize);
  526.  
  527.         //--- create initial stack
  528.         asm { sti; }                            // ensure interrupts enabled!
  529.         *--(DOSThread**)(entry->stkptr) = this; // parameter for "start"
  530.         entry->stkptr -= 2;                     // dummy return address
  531.         *--(entry->stkptr) = _FLAGS;            // flags
  532.         *--(entry->stkptr) = FP_SEG (&DOSThreadManager::start);   // cs
  533.         *--(entry->stkptr) = FP_OFF (&DOSThreadManager::start);   // ip
  534.         entry->stkptr -= 5;                     // ax, bx, cx, dx, es
  535.         *--(entry->stkptr) = _DS;               // ds
  536.         entry->stkptr -= 2;                     // si, di
  537.         *--(entry->stkptr) = _BP;               // bp
  538.  
  539.         //--- stack extended registers on a 386 or above
  540.         if (i386)
  541.             entry->stkptr -= 18;                // 8 x 32-bit regs, fs, gs
  542.     }
  543.  
  544.     //--- allow thread to live (but don't move it into any queue)
  545.     state = CREATED;
  546. }
  547.  
  548. //--------------------------------------------------------------------------
  549. //
  550. //      DOSThread::~DOSThread.
  551. //
  552. //      Wait for current thread to terminate, and then destroy the evidence.
  553. //
  554. DOSThread::~DOSThread ()
  555. {
  556.     //--- wait for thread to terminate (normal threads only)
  557.     if (this != mainthread && this != ready->thread)
  558.         wait ();
  559.  
  560.     //--- delete associated structures
  561.     delete entry;
  562.     delete stack;
  563.  
  564.     //--- register thread destruction (normal threads only)
  565.     if (this != mainthread && this != ready->thread)
  566.         DOSThreadManager::destroy ();
  567. }
  568.  
  569.  
  570. //--------------------------------------------------------------------------
  571. //
  572. //      DOSThread::wait.
  573. //
  574. //      Wait for thread to terminate.  This is needed to allow destructors
  575. //      to avoid destroying threads while they are still running.
  576. //
  577. void DOSThread::wait ()
  578. {
  579.     //--- make sure a thread is not trying to wait on itself
  580.     if (this == current->thread)
  581.         return;
  582.  
  583.     //--- terminate task if it hasn't started yet
  584.     if (state == CREATED)
  585.         state = TERMINATED;
  586.  
  587.     //--- wait for thread to terminate
  588.     while (state != TERMINATED)
  589.         pause ();
  590. }
  591.  
  592. //--------------------------------------------------------------------------
  593. //
  594. //      DOSThread::userbreak.
  595. //
  596. //      This function returns the value of the flag which indicates if
  597. //      control-break has been pressed.
  598. //
  599. int DOSThread::userbreak ()
  600. {
  601.     return breakflag;
  602. }
  603.  
  604.  
  605. //--------------------------------------------------------------------------
  606. //
  607. //      DOSThread::cancelbreak.
  608. //
  609. //      This function resets the flag which indicates if control-break has
  610. //      been pressed.  It also returns the original value of the flag.
  611. //
  612. int DOSThread::cancelbreak ()
  613. {
  614.     DISABLE;
  615.     int b = breakflag;
  616.     breakflag = 0;
  617.     ENABLE;
  618.  
  619.     return b;
  620. }
  621.  
  622.  
  623. //--------------------------------------------------------------------------
  624. //
  625. //      DOSThread::run.
  626. //
  627. //      Start a new thread running.
  628. //
  629. int DOSThread::run ()
  630. {
  631.     //--- error if thread is not newly created
  632.     if (state != CREATED)
  633.         return 0;
  634.  
  635.     //--- make thread ready to run and start it running
  636.     entry->move (ready->next, READY);
  637.     DOSThreadManager::schedule ();
  638.     return 1;
  639. }
  640.  
  641.  
  642. //--------------------------------------------------------------------------
  643. //
  644. //      DOSThread::terminate.
  645. //
  646. //      Immediately terminate a thread.  The thread is detached from its
  647. //      current queue ready to be destroyed.
  648. //
  649. void DOSThread::terminate ()
  650. {
  651.     entry->move (0, TERMINATED);
  652.     DOSThreadManager::schedule ();
  653. }
  654.  
  655. //--------------------------------------------------------------------------
  656. //
  657. //      DOSThread::delay.
  658. //
  659. //      Delay for "n" clock ticks.  The current thread is moved to the
  660. //      correct position in the "delayed" queue and will be woken up
  661. //      by the timer interrupt handler.
  662. //
  663. void DOSThread::delay (int n)
  664. {
  665.     //--- don't delay if no current thread, or if tick count is non-positive
  666.     if (current == 0 || n <= 0)
  667.         return;
  668.  
  669.     //--- set wake-up time
  670.     current->wakeup = *currtime + n;
  671.  
  672.     //--- find correct position in delay queue
  673.     DOSThreadManager* t = delayed->next;
  674.     while (t != delayed && t->wakeup < current->wakeup)
  675.         t = t->next;
  676.  
  677.     //--- put thread in delay queue and reschedule
  678.     current->move (t, DELAYED);
  679.     DOSThreadManager::schedule ();
  680. }
  681.  
  682.  
  683. //--------------------------------------------------------------------------
  684. //
  685. //      DOSThread::pause.
  686. //
  687. //      Move the current thread to the back of the ready queue.
  688. //
  689. void DOSThread::pause ()
  690. {
  691.     //--- don't pause if no current thread
  692.     if (current == 0)
  693.         return;
  694.  
  695.     //--- move current thread to back of ready queue and reschedule
  696.     current->move (ready, READY);
  697.     DOSThreadManager::schedule ();
  698. }
  699.  
  700.  
  701. //--------------------------------------------------------------------------
  702. //
  703. //      DOSThread::timeslice.
  704. //
  705. //      Set the timeslice.  This is ignored once the first thread has
  706. //      been created.
  707. //
  708. void DOSThread::timeslice (unsigned n)
  709. {
  710.     if (threadcount == 0)
  711.         slicesize = n;
  712. }
  713.  
  714. //--------------------------------------------------------------------------
  715. //
  716. //      Constructors and destructors for DOSMonitorQueue and DOSMonitor.
  717. //
  718. //      These are trivial but involve knowledge of DOSThreadManager, and
  719. //      so cannot go in the header file.
  720. //
  721. DOSMonitorQueue::DOSMonitorQueue ()     : queue (new DOSThreadManager)  { }
  722. DOSMonitorQueue::~DOSMonitorQueue ()    { delete queue; }
  723. DOSMonitor::DOSMonitor ()               : lockq (new DOSThreadManager),
  724.                                           lockholder (0)                { }
  725. DOSMonitor::~DOSMonitor ()              { delete lockq; }
  726.  
  727.  
  728. //--------------------------------------------------------------------------
  729. //
  730. //      DOSMonitor::lock.
  731. //
  732. //      This ensures that only one thread at a time is executing in a
  733. //      monitor object, and must be called before any other monitor
  734. //      functions (suspend, resume or unlock) can be used.  If another
  735. //      (non-terminated) thread already holds the lock, the current
  736. //      thread is suspended by being placed at the back of the monitor
  737. //      lock queue.  When the current lock holder calls "unlock", all
  738. //      queued threads are made ready, and the thread can then attempt
  739. //      to acquire the lock again.  The current thread must not be the
  740. //      current lock holder (i.e. no recursive monitor calls are allowed).
  741. //
  742. void DOSMonitor::lock ()
  743. {
  744.     //--- check for errors
  745.     if (lockq == 0)
  746.         error (NEW_FAIL);               // lock queue doesn't exist
  747.     if (current == 0)
  748.         error (NO_THREAD);              // no current thread
  749.     if (lockholder == current)
  750.         error (LOCK_FAIL);              // current thread already holds lock
  751.  
  752.     DISABLE;
  753.  
  754.     //--- suspend repeatedly until lock is available
  755.     while (lockholder != 0 &&
  756.            lockholder->thread->status() != DOSThread::TERMINATED)
  757.     {   current->move (lockq, DOSThread::WAITING);
  758.         DOSThreadManager::schedule ();
  759.     }
  760.  
  761.     //--- make current thread the new lock holder
  762.     lockholder = current;
  763.  
  764.     ENABLE;
  765. }
  766.  
  767. //--------------------------------------------------------------------------
  768. //
  769. //      DOSMonitor::unlock.
  770. //
  771. //      The calling thread must be the current lock holder.  The lock is
  772. //      released, and all threads requesting a lock are moved to the front
  773. //      of the ready queue.
  774. //
  775. void DOSMonitor::unlock ()
  776. {
  777.     //--- check for errors
  778.     if (lockholder != current)
  779.         error (UNLOCK_FAIL);            // current thread isn't the lock holder
  780.  
  781.     DISABLE;
  782.  
  783.     //--- release lock
  784.     lockholder = 0;
  785.  
  786.     //--- make pending threads ready to run
  787.     DOSThreadManager* t = ready->next;
  788.     while (lockq->next != lockq)
  789.         lockq->next->move (t, DOSThread::READY);
  790.  
  791.     //--- reschedule
  792.     DOSThreadManager::schedule ();
  793.  
  794.     ENABLE;
  795. }
  796.  
  797. //--------------------------------------------------------------------------
  798. //
  799. //      DOSMonitor::suspend.
  800. //
  801. //      This function allows a monitor to suspend the current thread on
  802. //      a monitor queue until another thread resumes it.  The current
  803. //      thread must be the current lock holder for the monitor.  The
  804. //      lock is released while the thread is suspended.
  805. //
  806. void DOSMonitor::suspend (DOSMonitorQueue& q)
  807. {
  808.     //--- check for errors
  809.     if (q.queue == 0)
  810.         error (NEW_FAIL);               // monitor queue doesn't exist
  811.     if (lockholder != current)
  812.         error (SUSPEND_FAIL);           // current thread isn't the lock holder
  813.  
  814.     //--- release lock
  815.     unlock ();
  816.  
  817.     //--- suspend current thread and reschedule
  818.     current->move (q.queue, DOSThread::SUSPENDED);
  819.     DOSThreadManager::schedule ();
  820.  
  821.     //--- lock the monitor again
  822.     lock ();
  823. }
  824.  
  825.  
  826. //--------------------------------------------------------------------------
  827. //
  828. //      DOSMonitor::resume.
  829. //
  830. //      This function allows a monitor to resume any threads suspended
  831. //      on a monitor queue.  The current thread must be the current lock
  832. //      holder for the monitor.
  833. //
  834. void DOSMonitor::resume (DOSMonitorQueue& q)
  835. {
  836.     //--- check for errors
  837.     if (q.queue == 0)
  838.         error (NEW_FAIL);               // monitor queue doesn't exist
  839.     if (lockholder != current)
  840.         error (RESUME_FAIL);            // current thread isn't the lock holder
  841.  
  842.     //--- make any suspended threads wait for the lock to be released
  843.     DOSThreadManager* lq = lockq->next;
  844.     while (q.queue != q.queue->next)
  845.         q.queue->next->move (lq, DOSThread::WAITING);
  846. }
  847.